package net.gdface.sdk;

import static net.gdface.utils.ConditionChecks.checkNotNull;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.gdface.image.ImageErrorException;
import net.gdface.image.MatType;
import net.gdface.image.BaseLazyImage;
import net.gdface.image.UnsupportedFormatException;
import net.gdface.sdk.fse.CodeBean;
import net.gdface.sdk.fse.FeatureSe;
import net.gdface.utils.Assert;
import net.gdface.utils.ShareLock;

/**
 * {@link FaceApi}本地实现<br>
 * SDK接口类本地实现必须从此类派生<br>
 * @author guyadong
 *
 */
public abstract class BaseFaceApiLocal extends BaseFaceApi implements CapacityFieldConstant {
	private final FeatureSe fse;

	protected final Map<String, String> capacity = new HashMap<>();
	private final Map<String, String> roCapacity = Collections.unmodifiableMap(capacity);
	/**
	 * 并发锁,限制建模的并发线程数,默认为CPU核数
	 */
	protected static ShareLock concurrentLock = new ShareLock(Runtime.getRuntime().availableProcessors());
	protected static IAuxTool auxTool = new IAuxTool() {		
		@Override
		public void saveOnFail(String phase, BaseLazyImage lazyImage) {
			// DO NOTHING
		}
	};
	public static synchronized void setAuxTool(IAuxTool auxTool) {
		if(null != auxTool){
			BaseFaceApiLocal.auxTool = auxTool;
		}
	}
	protected BaseFaceApiLocal() {
		capacity.put(C_MULTI_FACE_FEATURE, Boolean.FALSE.toString());
		capacity.put(C_WEAR_MASK, Boolean.FALSE.toString());
		fse = getFeatureSeInstance(this);		
		capacity.put(C_FSE_ENABLE, fse == null ? Boolean.FALSE.toString(): Boolean.TRUE.toString());
		capacity.put(C_CODEINFO_RELOCATE, Boolean.FALSE.toString());
		capacity.put(C_LOCAL_DETECT, Boolean.TRUE.toString());
	}
	@Override
	public CodeInfo[] detectAndGetCodeInfo(byte[] imgData, int faceNum) throws ImageErrorException, NotFaceDetectedException {
		BaseLazyImage lazyImg = makeOpenedLazyImage(imgData);
		try {
			return detectAndGetCodeInfo(lazyImg, faceNum);
		} finally {
			try {
				lazyImg.close();
				lazyImg = null;
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	/**
	 * 对图像({@code lazyImg})中指定的人脸位置{@code facePos}并提取特征码，返回生成人脸特征码数据数组对象（人脸可能不止一个）<br>
	 * 调用该方法时假设图像({@code lazyImage})能正常解码,所以当对图像解码出现异常时，将异常对象封装到{@link RuntimeException}抛出<br>
	 * 
	 * @param lazyImg
	 *            图像对象,为{@code null}则抛出 {@link IllegalArgumentException}
	 * @param faceNum
	 * @param facePos
	 * @return 返回{@link CodeInfo}对象列表<br>
	 *         对应于{@code facePos}中的每个{@link FRect}对象,返回一个{@link CodeInfo}对象(数组下标相同):<br>
	 *         如果{@link FRect}对象为{@code null},则返回一个所有成员都为{@code null}的{@link CodeInfo}对象<br>
	 *         如果在{@link FRect}指定的范围没有提取到特征码,则{@link CodeInfo#getCode()},{@link CodeInfo#getEi()}返回{@code null}
	 * @throws NotFaceDetectedException 
	 * @see net.gdface.sdk.FaceApi#getCodeInfo(byte[], int, CodeInfo[])
	 */
	public List<CodeInfo> getCodeInfo(BaseLazyImage lazyImg, int faceNum, List<CodeInfo> facePos) throws NotFaceDetectedException {
		concurrentLock.lock();
		try {
			Assert.notNull(lazyImg, "lazyImg");
			byte[] imgData=lazyImg.getMatrixData(getNativeMatrixType());
			return nativeGetFaceFeatures(getNativeMatrixType(), imgData, lazyImg.getWidth(),lazyImg.getHeight(),faceNum, facePos);
		} catch (UnsupportedFormatException e) {
			throw new RuntimeException(e);
		} finally {
			concurrentLock.unlock();
		}	
	}
	/**
	 * 提取单个人脸特征 
	 * @param lazyImg
	 * @param facePos
	 * @return 人脸位置信息对象(包含特征),提取特征失败返回{@code null}
	 */
	public CodeInfo getCodeInfo(BaseLazyImage lazyImg, CodeInfo facePos){
		concurrentLock.lock();
		try {
			byte[] imgData=lazyImg.getMatrixData(getNativeMatrixType());
			byte[] feature = nativeGetFaceFeature(getNativeMatrixType(), imgData, lazyImg.getWidth(), lazyImg.getHeight(), facePos);
			facePos.setCode(feature);
			return feature == null ? null : facePos;
		} catch (UnsupportedFormatException e) {
			return null;
		} finally {
			concurrentLock.unlock();
		}
	}
	@Override
	public CodeInfo getCodeInfo(byte[] imgData, CodeInfo facePos){
		 try{
			BaseLazyImage lazyImg = makeOpenedLazyImage(imgData);
			 return getCodeInfo(lazyImg, facePos);
		 }catch (ImageErrorException e) {
			 throw new RuntimeException(e);
		}		
	}

	@Override
	public Boolean wearMask(byte[] imgData, CodeInfo faceInfo) throws ImageErrorException{
		BaseLazyImage lazyImg = makeOpenedLazyImage(imgData);
		try {
			return matWearMask(getNativeMatrixType(), lazyImg.getMatrixData(getNativeMatrixType()), lazyImg.getWidth(), lazyImg.getHeight(), faceInfo);
		} finally {
			try {
				lazyImg.close();
				lazyImg = null;
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}
	/**
	 * 先对图像数据{@code imgData}进行人脸检测，然后提取人脸特征码<br>
	 * 
	 * @param lazyImg
	 * @param faceNum
	 * @return 人脸位置信息对象(包含特征)数组
	 * @throws ImageErrorException
	 * @throws NotFaceDetectedException 没有检测人脸或提取到特征码的人脸数目不是要求的人脸数目
	 * @see net.gdface.sdk.FaceApi#detectAndGetCodeInfo(byte[], int)
	 */
	public CodeInfo[] detectAndGetCodeInfo(BaseLazyImage lazyImg, int faceNum) throws ImageErrorException,
	NotFaceDetectedException {
		concurrentLock.lock();
		try{
			byte[] imgData = lazyImg.getMatrixData(getNativeMatrixType());
			CodeInfo[] codes = nativeDetectAndGetFeatures(getNativeMatrixType(), imgData, lazyImg.getWidth(), lazyImg.getHeight());
			// 没有检测到要求的人脸则抛出异常
			if(0==codes.length){
				throw new NotFaceDetectedException();
			}
			if(faceNum>0&&faceNum!=codes.length){
				throw new NotFaceDetectedException();
			}
			return codes;
		}finally{
			concurrentLock.unlock();
		}		
	}	

	@Override
	public CodeInfo[] detectFace(byte[] imgData) throws ImageErrorException {
		BaseLazyImage lazyImg = makeOpenedLazyImage(imgData);
		try {
			return detectFace(lazyImg).toArray(new CodeInfo[0]);
		} finally {
			try {
				lazyImg.close();
				lazyImg = null;
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	@Override
	public CodeInfo[] getCodeInfo(byte[] imgData, int faceNum, CodeInfo[] facePos) throws NotFaceDetectedException {
		BaseLazyImage lazyImg = null;
		try {
			lazyImg = makeOpenedLazyImage(imgData);
			return getCodeInfo(lazyImg, faceNum, null == facePos ? null : Arrays.asList(facePos)).toArray(
					new CodeInfo[0]);
		} catch (ImageErrorException e) {
			throw new RuntimeException(e);
		} finally {
			try {
				if (null != lazyImg) {
					lazyImg.close();
					lazyImg = null;
				}
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	@Override
	public final double compareCode(byte[] code1, byte[] code2) {
		Assert.assertValidCode(code1, code2);
		return nativeCompareCode(code1,code2);
	}
	@Override
	public CodeInfo[] matDetectFace(MatType matType,byte[] matData, int width, int height){
		concurrentLock.lock();
		try {
			List<CodeInfo> faceInfo = new ArrayList<CodeInfo>();
			nativeDetectFace(matType,matData,width,height, faceInfo);
			return faceInfo.toArray(new CodeInfo[0]);
		} finally {
			concurrentLock.unlock();
		}
	}
	@Override
	public CodeInfo[] matDetectFace(MatType matType,byte[] matData, int width, int height, int facenum) throws NotFaceDetectedException{
		CodeInfo[] codes = matDetectFace(matType,matData,width,height);
		if(facenum != 0 && facenum != codes.length){
			throw new NotFaceDetectedException();
		}
		int faceCount= codes.length;
		// 提取到的人脸特征数目不等于要求提取的特征码的数目,则抛出异常
		if ((facenum > 0)) {
			if (faceCount != facenum){
				throw new NotFaceDetectedException(faceCount);
			}
		} else if (0 == faceCount){
			throw new NotFaceDetectedException(faceCount);
		}
		return codes;
	}
	@Override
	public CodeInfo[] matGetCodeInfo(MatType matType, byte[] matData, int width,int height, int facenum, CodeInfo[] facePos) throws NotFaceDetectedException{
		concurrentLock.lock();
		try {			
			List<CodeInfo> codes = nativeGetFaceFeatures(matType, matData, width, height,facenum, Arrays.asList(checkNotNull(facePos,"facepos is null")));
			return codes.toArray(new CodeInfo[codes.size()]);
		} finally {
			concurrentLock.unlock();
		}
	}
	@Override
	public CodeInfo matGetCodeInfo(MatType matType, byte[] matData, int width,int height, CodeInfo facePos){
		concurrentLock.lock();
		try {			
			byte[] feature = nativeGetFaceFeature(matType, matData, width, height, checkNotNull(facePos,"facepos is null"));
			facePos.setCode(feature);
			return feature == null ? null : facePos;
		} finally {
			concurrentLock.unlock();
		}
	}
	@Override
	public CodeInfo[] matDetectAndGetCodeInfo(MatType matType, byte[] matData, int width, int height) {
		concurrentLock.lock();
		try{
			return nativeDetectAndGetFeatures(matType, matData,width, height);
		}finally{
			concurrentLock.unlock();
		}		
	}
	@Override
	public CodeInfo[] matDetectAndGetCodeInfo(MatType matType, byte[] matData, int width, int height, int faceNum) throws NotFaceDetectedException {
		CodeInfo[] codes = matDetectAndGetCodeInfo(matType, matData,width, height);
		// 没有检测到要求的人脸则抛出异常
		if(0==codes.length){
			throw new NotFaceDetectedException();
		}
		if(faceNum>0&&faceNum!=codes.length){
			throw new NotFaceDetectedException();
		}
		return codes;
	}

	@Override
	public Boolean matWearMask(MatType matType, byte[] matData, int width, int height, CodeInfo faceInfo){
		concurrentLock.lock();
		try {
			return nativeWearMask(matType, matData, width, height, faceInfo);
		} finally {
			concurrentLock.unlock();
		}
	}
	/**
	 * 返回当前{@link FaceApi}接口实例对应的搜索引擎接口实例，如果没有则返回{@code null}
	 */
	public FeatureSe getFeatureSe(){
		return null;
	}
	@Override
	public boolean isLocal() {
		return true;
	}
	@Override
	public Map<String, String> sdkCapacity(){
		return roCapacity;
	}
	/**
	 * 对图像({@code lazyImg})进行人脸检测，返回人脸位置数据对象{@link CodeInfo}列表<br>
	 * 
	 * @param lazyImg
	 *            图像对象,为{@code null}时抛出{@link IllegalArgumentException}
	 * @return 没有检测到人脸则返回空表
	 * @throws UnsupportedFormatException
	 */
	public List<CodeInfo> detectFace(BaseLazyImage lazyImg)
			throws UnsupportedFormatException {
		concurrentLock.lock();
		try {
			Assert.notNull(lazyImg, "bufImg");
			List<CodeInfo> faceInfo = new ArrayList<CodeInfo>();
			if (lazyImg.getWidth() > 0 && lazyImg.getHeight() > 0) {
				byte[] imgMatrix =lazyImg.getMatrixData(getNativeMatrixType());
				nativeDetectFace(getNativeMatrixType(),imgMatrix, lazyImg.getWidth(),lazyImg.getHeight(), faceInfo);
			}
			return faceInfo;
		} finally {
			concurrentLock.unlock();
		}
	}
	@Override
	protected MatType getNativeMatrixType(){
		return MatType.BGR;
	}

	/**
	 * 参见 {@link #compareCode(byte[], byte[])}
	 * @param code1
	 * @param code2
	 */
	protected abstract double nativeCompareCode(byte[] code1, byte[] code2) ;
	
	/**
	 * 检测人脸
	 * @param matType 图像矩阵类型
	 * @param imgMatrix 图像矩阵
	 * @param width 图像宽度
	 * @param height 图像高度
	 * @param faceInfo 保存检测到的人脸位置信息
	 */
	protected abstract void nativeDetectFace(MatType matType, byte[] imgMatrix, int width, int height, List<CodeInfo> faceInfo);
	/**
	 * 对faceinfo指定的人脸位置提取特征
	 * @param matType 图像矩阵类型
	 * @param imgMatrix 图像矩阵
	 * @param width 图像宽度
	 * @param height 图像高度
	 * @param faceInfo
	 * @return 人脸特征数据,提取特征失败则返回null
	 */
	protected abstract byte[] nativeGetFaceFeature(MatType matType, byte[] imgMatrix, int width, int height, CodeInfo faceInfo);
	/**
	 * 对图像矩阵中指定的人脸位置{@code facePos}并提取特征码，返回生成人脸特征码数据数组对象（人脸可能不止一个）<br>
	 * @param matType 图像矩阵类型
	 * @param imgMatrix 图像矩阵
	 * @param width 图像宽度
	 * @param height 图像高度
	 * @param faceNum
	 * @param facePos
	 * @return 返回{@link CodeInfo}对象列表<br>
	 *         对应于{@code facePos}中的每个{@link FRect}对象,返回一个{@link CodeInfo}对象(数组下标相同):<br>
	 *         如果{@link FRect}对象为{@code null},则返回一个所有成员都为{@code null}的{@link CodeInfo}对象<br>
	 *         如果在{@link FRect}指定的范围没有提取到特征码,则{@link CodeInfo#getCode()},{@link CodeInfo#getEi()}返回{@code null}
	 * @throws NotFaceDetectedException 
	 */
	protected List<CodeInfo> nativeGetFaceFeatures(MatType matType, byte[] imgMatrix, int width,int height, int faceNum, List<CodeInfo> facePos) throws NotFaceDetectedException {
		// 检测到的人脸数目
		int faceCount = 0;
		// 提取到特征码的人脸数目
		int featureCount=0;
		Assert.notNull(imgMatrix, "imgMatrix");
		// facePos为null或空时抛出异常
		Assert.notEmpty(facePos, "facePos");
		// 人脸位置对象数目小于要求提取的特征码的数目,则抛出异常
		if (facePos.size() < faceNum){
			throw new NotFaceDetectedException(facePos.size(), 0);
		}
		byte[][] features = nativeGetFaceFeatures(matType, imgMatrix, width,height, facePos);
		for(int i = 0 ;i<features.length;++i){
			if(null!=facePos.get(i)){
				++faceCount;
				if(null!=features[i]){
					++featureCount;
				}
			}
		}
		// 提取到的人脸特征数目不等于要求提取的特征码的数目,则抛出异常
		if ((faceNum > 0)) {
			if (featureCount != faceNum){
				throw new NotFaceDetectedException(faceCount, featureCount);
			}
		} else if (0 == featureCount){
			throw new NotFaceDetectedException(faceCount, 0);
		}
		for(int i=0;i<facePos.size();++i){
			CodeInfo ci = facePos.get(i);
			if(null!=ci){
				ci.setCode(features[i]);
			}
		}
		return facePos;

	}
	/**
	 * 根据检测到的人脸位置{@code facePos},提取一组特征
	 * @param matType 图像矩阵类型
	 * @param imgMatrix 图像矩阵
	 * @param width 图像宽度
	 * @param height 图像高度
	 * @param facePos
	 * @return 返回facePos列表中每个人脸位置对应的人脸特征数据，提取特征失败的对应为null
	 */
	protected byte[][] nativeGetFaceFeatures(MatType matType, byte[] imgMatrix, int width, int height, List<CodeInfo> facePos) {
		byte[][] res = new byte[facePos.size()][];
		for(int i=0;i<facePos.size();++i){
			CodeInfo face = facePos.get(i);
			if(null != face){
				res[i]=nativeGetFaceFeature(matType, imgMatrix, width, height, face);
				face.setCode(res[i]);
			}else{
				res[i] = null;
			}
		}		
		return res;			
	}
	/**
	 * 检测并提取特征
	 * @param matType 图像矩阵类型
	 * @param imgMatrix 图像矩阵
	 * @param width 图像宽度
	 * @param height 图像高度
	 * @return 包含人脸特征数据的{@code CodeInfo}数组
	 */
	protected CodeInfo[] nativeDetectAndGetFeatures(MatType matType, byte[] imgMatrix, int width, int height) {
		LinkedList<CodeInfo> faceInfo = new LinkedList<CodeInfo>();
		nativeDetectFace(matType, imgMatrix, width, height, faceInfo);
		for( CodeInfo face:faceInfo){
			face.setCode(nativeGetFaceFeature(matType, imgMatrix, width, height, face));
		}
		return faceInfo.toArray(new CodeInfo[faceInfo.size()]);
	}
	/**
	 * 对从视频流中直接获取的NV21图像进行人脸检测
	 * @param nv21 NV21格式图像
	 * @param width 图像宽度
	 * @param height 图像高度
	 * @param faceInfo 人脸信息对象列表
	 * @deprecated replaced by {@link #matDetectFace(MatType, byte[], int, int)}
	 */
	protected void nativeNv21DetectFace(byte[] nv21,int width, int height,List<CodeInfo> faceInfo){
		nativeDetectFace(MatType.NV21,nv21, width,height, faceInfo);
	}

	/**
	 * 根据facePos提供的人脸信息位置, 在NV21格式图像中提取特征码
	 * @param nv21 NV21格式图像
	 * @param width 图像宽度
	 * @param height 图像高度
	 * @param facePos 人脸位置信息对象,不可为{@code null}
	 * @return 包含人脸特征数据的{@code CodeInfo}对象
	 * @deprecated replaced by {@link #matGetCodeInfo(MatType, byte[], int, int, CodeInfo)}
	 */
	protected byte[] nativeNv21GetFaceFeature(byte[] nv21, int width, int height,CodeInfo facePos){
		return nativeGetFaceFeature(MatType.NV21,nv21, width, height, checkNotNull(facePos,"facepos is null"));
	}
	/**
	 * 根据facePos提供的人脸信息位置, 在NV21格式图像中提取特征码
	 * @param nv21 NV21格式图像
	 * @param width 图像宽度
	 * @param height 图像高度
	 * @param facePos 人脸位置信息对象,不可为{@code null}
	 * @return 包含人脸特征数据的{@code CodeInfo}对象
	 * @deprecated replaced by {@link #matGetCodeInfo(MatType, byte[], int, int, int, CodeInfo[])}
	 */
	protected byte[][] nativeNv21GetFaceFeatures(byte[] nv21, int width, int height,List<CodeInfo> facePos){
		return nativeGetFaceFeatures(MatType.NV21,nv21, width, height, checkNotNull(facePos,"facepos is null"));
	}
	/**
	 * 检测并提取特征
	 * @param nv21 NV21格式图像矩阵数据
	 * @param width 图像宽度
	 * @param height 图像高度
	 * @return 包含人脸特征数据的{@code CodeInfo}数组
	 * @deprecated replaced by {@link #matDetectAndGetCodeInfo(MatType, byte[], int, int)}
	 */
	protected CodeInfo[] nativeNv21DetectAndGetFeatures(byte[] nv21, int width, int height) {
		CodeInfo[] faceInfo = nativeDetectAndGetFeatures(MatType.NV21,nv21, width, height);
		return faceInfo;
	}
	/**
     * 戴口罩检测检测方法
	 * @param matType 图像矩阵类型
	 * @param imgMatrix 图像矩阵
	 * @param width  图像宽度
	 * @param height 图像高度
	 * @param faceInfo  可见光图像人脸信息
     *
     * @return 戴口罩返回{@code true},否则返回{@code false},{@code null} 不知道
     */
	protected Boolean nativeWearMask(MatType matType, byte[] imgMatrix, int width, int height, CodeInfo faceInfo){
		return null;
	}
	/**
     * 戴口罩检测检测方法
     *
     * @param nv21   NV21格式图像矩阵数据
     * @param width  图像宽度
     * @param height 图像高度
     * @param faceInfo  可见光图像人脸信息
     * @return 戴口罩返回{@code true},否则返回{@code false},{@code null} 不知道
     * @deprecated replaced by {@link #matWearMask(MatType, byte[], int, int, CodeInfo)}
     */
	protected Boolean nativeNv21WearMask(byte[] nv21, int width, int height, CodeInfo faceInfo){
		return nativeWearMask(MatType.NV21,nv21,width,height,faceInfo);
	}
	
	@Override
	public FseResult[] searchFeatures(byte[] feature, double similarity, int rows, String[] imgMD5Set, int group) {
		if(null != fse){
			return CodeBean.toFseResult(fse.searchCode(feature, similarity, rows, imgMD5Set, group));
		}
		throw new UnsupportedOperationException();
	}
	
	/**
	 * 绑定算法实例到当前线程<br>
	 * 为当前线程申请一个为当前线程专用的算法实例,直到调用{@link #unbindNativeInstanceForCurentThread()}时释放,
	 * 对于非线程安全的算法实例,在类似连续视频检测线程中,需要将算法实例绑定到指定的线程才能获取好的检测效果,
	 * 在这种应用场景下需要重写些方法
	 */
	public void bindNativeInstanceForCurentThread(){
		bindNativeDetectInstanceForCurentThread();
		bindNativeFeatureInstanceForCurentThread();
	}
	/**
	 * 解除绑定在当前线程上的实例归还到资源池 
	 */
	public void unbindNativeInstanceForCurentThread(){
		unbindNativeDetectInstanceForCurentThread();
		unbindNativeFeatureInstanceForCurentThread();
	}
	/**
	 * 绑定人脸检测算法实例到当前线程<br>
	 * 为当前线程申请一个为当前线程专用的人脸检测算法实例,直到调用{@link #unbindNativeDetectInstanceForCurentThread()}时释放,
	 * 对于非线程安全的算法实例,在类似连续视频检测线程中,需要将算法实例绑定到指定的线程才能获取好的检测效果,
	 * 在这种应用场景下需要重写些方法
	 */
	public void bindNativeDetectInstanceForCurentThread(){
	}
	/**
	 * 解除绑定在当前线程上的实例归还到资源池 
	 */
	public void unbindNativeDetectInstanceForCurentThread(){
	}
	/**
	 * 绑定特征提取算法实例到当前线程<br>
	 * 为当前线程申请一个为当前线程专用的特征提取算法实例,直到调用{@link #unbindNativeFeatureInstanceForCurentThread()}时释放,
	 * 对于非线程安全的算法实例,在类似连续视频检测线程中,需要将算法实例绑定到指定的线程才能获取好的检测效果,
	 * 在这种应用场景下需要重写些方法
	 */
	public void bindNativeFeatureInstanceForCurentThread(){
	}
	/**
	 * 解除绑定在当前线程上的实例归还到资源池 
	 */
	public void unbindNativeFeatureInstanceForCurentThread(){
	}
	public void reset(){
		
	}
}